package main

import (
	"TCP/CRC"
	"TCP/api"
	D "TCP/define"
	R "TCP/resources"
	"TCP/resources/db"
	"TCP/tool"
	"bytes"
	"encoding/binary"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"strconv"
	"syscall"
	"time"
	"xlib/log"

	"gopkg.in/ini.v1"
)

const (
	Wait   = 0 // 等待执行
	Exe    = 1 // 正在执行
	Finish = 2 // 执行完成
)

//Logger 日志的相关配置
type Logger struct {
	RollType string
	Dir      string
	File     string
	Count    int32
	Size     int64
	Unit     string
	Level    string
	Compress int64
}

//解析日志配置
type LogAnalysis struct {
	CheckDataLen string //校验数据长度
	CheckDataCRC string //校验校验码
	CodeFileLen  int    //日志所在文件名占位
	LineLen      int    //日志所在行数占位
}

//配置文件所在路径
var cfgPath = "config.ini"

//系统运行模式
var runMode string

//系统运行模式
var httpManage string

//存放当前进程运行的pid文件
var PidFile string

//Logger 日志的相关配置
var cfgLogger Logger

//日志转换配置
var cfgLogAnalysis LogAnalysis

//文件名编号
var CodeFileName map[int]string

//普通消息表
var MsgMap map[string]string

//带参数消息表
var ParaMsgMap map[string]string

//sim卡状态表
var SimStatus map[int]string

//mqtt错误码
var MQTTErrCodes map[int]string

//设备参数
var DeviceParam map[string]string

//服务器相关参数
var ServerType string
var ServerIp string
var ServerPort string
var ServerWebPort string

// Proto
const (
	AddrLen   = 11   //设备号长度
	StartCode = 0x68 // 帧头
	EndCode   = 0x16 // 帧尾
)

type ProFrame struct {
	Start byte          //帧头
	Len   uint16        //帧长度:不包含本身，从设备号开始直到结束码(不包含结束码) = 12 + N + 1  = n - 4
	Addr  [AddrLen]byte //设备号
	Cmd   byte          //功能码
	//校验码从设备号开始到校验码结束(不包含校验码)
}

// commands
const (
	Param  = 0x00 // 设备参数
	Upload = 0x01 // 上传报文
)

//帧头结构体
type LogFrame struct {
	Time  string  //时间
	File  string  //文件名
	Line  uint16  //行号
	Type  byte    //日志类型
	Log   [2]byte //日志内容
	UnUse byte    //保留字节
}

//写入log至指定文件
func outLogToFile(file string, LogStr string) {
	tmpF, err := os.OpenFile(file+".log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0777)
	defer tmpF.Close()
	if err != nil {
		log.Error("Open Log File err:", err)
		return
	}
	_, err = tmpF.Write([]byte(LogStr))
	if err != nil {
		log.Error("write log File err:", err)
		return
	}
}

//输出日志
func outOneDate(data []LogFrame, frame *ProFrame) {
	var LogStr string
	var FinalLogStr string
	var LogIdx string
	var LogPara uint16
	drvEvents := make([]db.DrvEvent, 0)
	formatStr := fmt.Sprintf("[%%%ds][%%%dd]<%%02X>", cfgLogAnalysis.CodeFileLen, cfgLogAnalysis.LineLen)
	for _, log := range data {
		logTime, _ := time.Parse(D.TimeDataFormat, log.Time)
		drvEvent := db.DrvEvent{
			SN:        fmt.Sprintf("%s", frame.Addr),
			EventTime: logTime,
			File:      log.File,
			Line:      int(log.Line),
			Type:      int(log.Type),
		}
		LogStr = log.Time + fmt.Sprintf(formatStr, log.File, log.Line, log.Type)
		//判断类型转换日志内容
		if log.Type == 0x01 { //普通消息表
			LogIdx = fmt.Sprintf("%02X%02X", log.Log[0], log.Log[1])
			drvEvent.EventID = LogIdx
			if msg, isOk := MsgMap[LogIdx]; isOk {
				LogStr += msg
			} else {
				LogStr += fmt.Sprintf("unknow msg:%s, UnUse:%02X", LogIdx, log.UnUse)
			}
		} else if log.Type == 0x02 { //带参数消息表
			LogIdx = fmt.Sprintf("%02X", log.Log[0])
			LogPara = (uint16(log.Log[1]) << 8) + uint16(log.UnUse)
			drvEvent.EventID = LogIdx
			drvEvent.EventPara = int(LogPara)
			if msg, isOk := ParaMsgMap[LogIdx]; isOk {
				//sim卡状态转换
				if LogIdx == "05" || LogIdx == "06" || LogIdx == "07" {
					LogStr += fmt.Sprintf(msg, LogPara)
					if simStat, ok := SimStatus[int(LogPara)]; ok {
						LogStr += " " + simStat
					}
				} else if LogIdx == "41" { //mqtt错误码转换
					LogStr += fmt.Sprintf(msg, LogPara)
					if MQTTErrCode, ok := MQTTErrCodes[int(LogPara)]; ok {
						LogStr += " " + MQTTErrCode
					}
				} else {
					LogStr += fmt.Sprintf(msg, LogPara)
				}
			} else {
				LogStr += fmt.Sprintf("unknow msg:%s, Para:%04X", LogIdx, LogPara)
			}
		} else {
			LogStr += fmt.Sprintf("unknow msg type:%d, msg:%s, UnUse:%02X", log.Type, LogIdx, log.UnUse)
		}
		drvEvent.Desc = LogStr
		LogStr += "\n"
		FinalLogStr += LogStr
		drvEvents = append(drvEvents, drvEvent)
	}
	outLogToFile(string(frame.Addr[:]), FinalLogStr)
	R.SaveDrvEvent(drvEvents)
}

//解析一段数据
func deOneDate(buf []byte, oneDLen int, num int) []LogFrame {
	idx := 0
	data := make([]LogFrame, num)
	nowDate := time.Now().Format("2006/01/")
	for i := 0; i < num; i++ {
		data[i].Time = nowDate + fmt.Sprintf("%02X %02X:%02X:%02X", buf[idx], buf[idx+1], buf[idx+2], buf[idx+3])
		fileIdx := int(buf[idx+4] >> 5)
		data[i].File = CodeFileName[fileIdx]
		data[i].Line = (uint16(buf[idx+4]&0x1F) << 8) + uint16(buf[idx+5])
		data[i].Type = buf[idx+6]
		data[i].Log[0] = buf[idx+7]
		data[i].Log[1] = buf[idx+8]
		data[i].UnUse = buf[idx+9]
		idx += oneDLen
	}
	debugPrintf("%#v", data)
	log.Debugf("%#v", data)
	return data
}

//回复设备数据
func replyCMD(frame *ProFrame) []byte {
	//68 00 2C 4B 44 35 38 36 31 30 30 30 30 31 81 21 16
	frame.Len = uint16(binary.Size(frame)) - 2
	frame.Cmd = frame.Cmd | 0x80
	var packSend []byte

	buf := new(bytes.Buffer)
	err := binary.Write(buf, binary.BigEndian, frame)
	if err != nil {
		log.Error("replyCMD binary.Write error:", err)
		return nil
	}
	packSend = append(buf.Bytes())
	crc := CRC.ADD8(packSend, 3, len(packSend))
	packSend = append(packSend, crc)
	packSend = append(packSend, EndCode)

	return packSend
}

//验证帧结构
func CheckCmd(buf []byte, len int) (bool, *ProFrame) {
	frame := &ProFrame{}
	tool.Convert(buf[:binary.Size(frame)], binary.BigEndian, frame)
	//检查帧头帧尾
	if frame.Start != StartCode || buf[len-1] != EndCode {
		log.Error("err Start or End Code")
		return false, &ProFrame{}
	}
	//检查数据长度
	if cfgLogAnalysis.CheckDataLen == "true" {
		if frame.Len != uint16(len-4) {
			log.Error("err frame len")
			return false, &ProFrame{}
		}
	}
	//计算校验码
	if cfgLogAnalysis.CheckDataCRC == "true" {
		crc := CRC.ADD8(buf, 3, len-2)
		if buf[len-2] != crc {
			log.Errorf("rev CRC:%X,real:%X", buf[len-2], crc)
			return false, &ProFrame{}
		}
	}
	log.Debugf("%#v", frame)
	if frame.Cmd == Upload { //上传报文
		/*示例报文
		68 00 2C
		4B 44 35 38 36 31 30 30 30 30 31
		01 0A
		14 22 28 30 20 07 01 44 44 55
		14 22 29 30 40 07 02 44 44 52
		14 22 30 30 20 07 01 44 44 55
		3F
		16
		*/
		oneDLen := int(buf[binary.Size(frame)])
		//remainLen := frame.Len - AddrLen - 3 //减去cmd 数据长度 校验码
		remainLen := len - binary.Size(frame) - 3 //减去数据长度 校验码 帧尾
		num := remainLen / (int(oneDLen))
		log.Debugf("oneDLen:%d, remainLen:%d, num:%d", oneDLen, remainLen, num)
		data := deOneDate(buf[binary.Size(frame)+1:], oneDLen, num)
		outOneDate(data, frame)
	} else if frame.Cmd == Param { //设备参数
		/*示例报文
		68 00 6A
		4B 44 35 38 36 31 30 30 30 30 31
		00
		22 02 21 20 18 00
		00 01 02
		01 04 4B 44 35 38
		02 06 56 31 2E 30 2E 31
		03 01 0B
		04 01 5D
		05 0F 38 36 31 33 30 30 30 34 36 30 34 36 36 36 32
		06 0F 38 36 31 33 30 30 30 34 36 30 34 36 36 36 32
		07 0F 38 36 31 33 30 30 30 34 36 30 34 36 36 36 32
		08 0B 31 38 35 37 34 33 32 34 39 31 33
		2F
		16
		*/
		DrvInfo := db.DrvInfo{
			SN: fmt.Sprintf("%s", frame.Addr),
		}
		var LogStr string
		idxLen := binary.Size(frame)
		LogStr = fmt.Sprintf("[%02X/%02X/%02X %02X:%02X:%02X] 接收到设备参数\n", buf[idxLen], buf[idxLen+1], buf[idxLen+2], buf[idxLen+3], buf[idxLen+4], buf[idxLen+5])
		DrvInfo.DrvTime, _ = time.Parse(D.TimeDataFormat, fmt.Sprintf("20%02X/%02X/%02X %02X:%02X:%02X", buf[idxLen], buf[idxLen+1], buf[idxLen+2], buf[idxLen+3], buf[idxLen+4], buf[idxLen+5]))
		var Key string
		for idxLen += 6; idxLen < len-2; {
			Key = fmt.Sprintf("%02X", buf[idxLen])
			paramName, ok1 := DeviceParam[Key]
			paramType, ok2 := DeviceParam["type"+Key]
			if ok1 && ok2 {
				if paramType == "string" {
					LogStr += fmt.Sprintf("%s: %s\n", paramName, buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				} else if paramType == "u8" {
					LogStr += fmt.Sprintf("%s: %d\n", paramName, buf[idxLen+2])
				} else {
					LogStr += fmt.Sprintf("%s: err type %s\n", paramName, paramType)
				}
			} else {
				LogStr += fmt.Sprintf("unknown type:%02X, len:%d, Data:%s\n", buf[idxLen], buf[idxLen+1], tool.HexToHexStr(buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))]))
			}
			switch Key {
			case "00":
				DrvInfo.SimStatus = int(buf[idxLen+2])
				break
			case "01":
				DrvInfo.ModType = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "02":
				DrvInfo.ModVER = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "03":
				DrvInfo.CSQ = int(buf[idxLen+2])
				break
			case "04":
				DrvInfo.Rate = int(buf[idxLen+2])
				break
			case "05":
				DrvInfo.IMEI = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "06":
				DrvInfo.IMSI = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "07":
				DrvInfo.ICCID = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "08":
				DrvInfo.PhoneNum = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "09":
				DrvInfo.BootVer = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "0A":
				DrvInfo.CoreVer = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "0B":
				DrvInfo.TeVer = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "0C":
				DrvInfo.ModSdkVer = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "0D":
				DrvInfo.ModCoreVer = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			case "0E":
				DrvInfo.FactoryMode = fmt.Sprintf("%s", buf[idxLen+2:(idxLen+2+int(buf[idxLen+1]))])
				break
			}
			idxLen += 2 + int(buf[idxLen+1])
		}
		outLogToFile(string(frame.Addr[:]), LogStr)
		R.SaveDrvInfo(DrvInfo)
	}
	return true, frame
}
func process(con net.Conn) { //多线程接收数据
	//循环接收客户端发送的数据
	defer con.Close() //关闭con
	for {
		//创建一个新的切片
		buf := make([]byte, 1024)
		//1.等待客户端通过con发送信息
		//2.如果客户端没有write[发送]，协程就会阻塞于此
		n, err := con.Read(buf)
		if err != nil {
			debugPrintln("客户端已退出,err:", err)
			log.Info(con.RemoteAddr().String(), "客户端已退出,err:", err)
			return
		} else {
			//3.服务器显示客户端信息
			debugPrintf("RECV len：%03d data：%s\n", n, tool.HexToHexStr(buf[:n]))
			log.Infof("RECV len：%03d data：%s", n, tool.HexToHexStr(buf[:n]))

			if isOk, frame := CheckCmd(buf[:n], n); isOk {
				sendData := replyCMD(frame)
				if len(sendData) > 0 {
					debugPrintf("SEND：len：%03d data：%s\n", len(sendData), tool.HexToHexStr(sendData))
					log.Infof("SEND：len：%03d data：%s\n", len(sendData), tool.HexToHexStr(sendData))
					con.Write(sendData)
				}
			}
		}
	}

}

//调试
//初始化日志
func LogInit() {
	//err := log.SetLogger("RollingFile", ".\\", "self.log", 5, 5, "MB", "debug", 0)
	err := log.SetLogger(cfgLogger.RollType, cfgLogger.Dir, cfgLogger.File, cfgLogger.Count, cfgLogger.Size, cfgLogger.Unit, cfgLogger.Level,
		cfgLogger.Compress)
	if err != nil {
		fmt.Print("Init SetLogger err:", err.Error())
		return
	}
}

//调试打印
func debugPrintf(format string, a ...interface{}) {
	if runMode == "Debug" {
		fmt.Printf(format, a...)
	}
}
func debugPrintln(a ...interface{}) {
	if runMode == "Debug" {
		fmt.Println(a...)
	}
}

//初始化配置文件
func ConfigInit() {
	FileName := make(map[int]string)
	cfgFile, err := ini.Load(cfgPath)
	if err != nil {
		debugPrintln("ConfigInit err:", err.Error())
		log.Error(err)
	}
	runMode = cfgFile.Section("Program").Key("runMode").String()
	httpManage = cfgFile.Section("Program").Key("httpManage").String()
	D.DBStorage = cfgFile.Section("Program").Key("DBStorage").String()
	PidFile = cfgFile.Section("Program").Key("pidFile").String()
	D.APNPath = cfgFile.Section("Program").Key("apnPath").String()

	for i := 0; i < 9; i++ {
		FileName[i] = cfgFile.Section("CodeFileName").Key("Name" + strconv.Itoa(i)).String()
	}
	CodeFileName = FileName
	//debugPrintf("CodeFileName %#v\n", CodeFileName)

	D.DbPara.Type = cfgFile.Section("DB").Key("type").String()
	D.DbPara.User = cfgFile.Section("DB").Key("user").String()
	D.DbPara.Pwd = cfgFile.Section("DB").Key("pwd").String()
	D.DbPara.IP = cfgFile.Section("DB").Key("ip").String()
	D.DbPara.Port = cfgFile.Section("DB").Key("port").String()
	D.DbPara.Database = cfgFile.Section("DB").Key("database").String()
	D.DbPara.Charset = cfgFile.Section("DB").Key("charset").String()
	//debugPrintf("DbPara %#v\n", D.DbPara)

	Logger := Logger{
		cfgFile.Section("Log").Key("rollType").String(),
		cfgFile.Section("Log").Key("dir").String(),
		cfgFile.Section("Log").Key("file").String(),
		0,
		0,
		cfgFile.Section("Log").Key("unit").String(),
		cfgFile.Section("Log").Key("level").String(),
		0,
	}
	count, _ := cfgFile.Section("Log").Key("count").Int64()
	Logger.Count = int32(count)
	Logger.Size, _ = cfgFile.Section("Log").Key("size").Int64()
	Logger.Compress, _ = cfgFile.Section("Log").Key("compress").Int64()
	cfgLogger = Logger
	//debugPrintf("%#v\n", cfgLogger)

	ServerType = cfgFile.Section("Net").Key("type").String()
	ServerIp = cfgFile.Section("Net").Key("ip").String()
	ServerPort = cfgFile.Section("Net").Key("port").String()
	ServerWebPort = cfgFile.Section("Net").Key("webPort").String()

	cfgLogAnalysis.CodeFileLen, _ = cfgFile.Section("LogAnalysis").Key("codeFileLen").Int()
	cfgLogAnalysis.LineLen, _ = cfgFile.Section("LogAnalysis").Key("lineLen").Int()
	cfgLogAnalysis.CheckDataLen = cfgFile.Section("LogAnalysis").Key("checkDataLen").String()
	cfgLogAnalysis.CheckDataCRC = cfgFile.Section("LogAnalysis").Key("checkDataCRC").String()

	MsgMap = make(map[string]string, 0)
	msgMapKeys := cfgFile.Section("MsgMap").KeyStrings()
	for _, key := range msgMapKeys {
		MsgMap[key] = cfgFile.Section("MsgMap").Key(key).String()
	}
	//debugPrintf("%#v\n", MsgMap)

	ParaMsgMap = make(map[string]string, 0)
	paraMsgMapKeys := cfgFile.Section("ParaMsgMap").KeyStrings()
	for _, key := range paraMsgMapKeys {
		ParaMsgMap[key] = cfgFile.Section("ParaMsgMap").Key(key).String()
	}
	//debugPrintf("%#v\n", ParaMsgMap)

	SimStatus = make(map[int]string, 0)
	simStatusKeys := cfgFile.Section("SimStatus").KeyStrings()
	for idx, key := range simStatusKeys {
		SimStatus[idx+1] = cfgFile.Section("SimStatus").Key(key).String()
	}
	//debugPrintf("%#v\n", SimStatus)

	MQTTErrCodes = make(map[int]string, 0)
	MQTTErrCodeKeys := cfgFile.Section("MQTTErrCode").KeyStrings()
	for idx, key := range MQTTErrCodeKeys {
		MQTTErrCodes[idx+1] = cfgFile.Section("MQTTErrCode").Key(key).String()
	}
	//debugPrintf("%#v\n", MQTTErrCode)

	DeviceParam = make(map[string]string, 0)
	deviceParamKeys := cfgFile.Section("DeviceParam").KeyStrings()
	for _, key := range deviceParamKeys {
		DeviceParam[key] = cfgFile.Section("DeviceParam").Key(key).String()
	}
	//debugPrintf("%#v\n", DeviceParam)

	debugPrintln("ConfigInit success")
}

//参数识别
func Daemonize(args ...string) {
	sysType := runtime.GOOS
	//后台运行
	if sysType == "linux" {
		var arg []string
		if len(args) > 1 {
			arg = args[1:]
		}
		fmt.Println("run in backModed")
		cmd := exec.Command(args[0], arg...)
		cmd.Env = os.Environ()
		cmd.Start()
	} else if sysType == "windows" {

	}
}
func main() {
	/*
		-d 后台运行
	*/
	args := os.Args
	daemon := false
	for k, v := range args {
		if v == "-d" {
			daemon = true
			args[k] = ""
		} else if v == "restart" {
			daemon = true
			args[k] = ""
		}
	}
	if daemon {
		Daemonize(args...)
		return
	}
	ConfigInit()
	LogInit()
	if PidFile != "" {
		// 写pid文件
		if err := ioutil.WriteFile(PidFile, []byte(strconv.Itoa(os.Getpid())), 0777); err != nil {
			log.Error("Create pid file", PidFile, err.Error())
			return
		}
		log.Warn("Create pid file", PidFile, "success")
		defer func() {
			if err := os.Remove(PidFile); err != nil {
				log.Error("remove pid file failed", err)
			} else {
				log.Warn("remove pid file success")
			}
		}()
	}
	debugPrintln("服务器开始监听...")
	//1.tcp表示使用网络协议是tcp
	//2.0.0.0.0:8888表示在本地监听8888端口
	lister, err := net.Listen(ServerType, ServerIp+":"+ServerPort)
	if err != nil {
		debugPrintln("监听失败...err: ", err)
		log.Error("监听失败...err: ", err)
		return
	}

	defer lister.Close() //延时关闭listen

	//循环等待客户端连接
	go func() {
		for {
			//等待客户端连接
			conn, err := lister.Accept()
			if err != nil {
				debugPrintf("连接Accept() 失败，err: ", err)
				log.Errorf("连接Accept() 失败，err: ", err)
			} else {
				debugPrintf("客户端%v 连接成功，conn=%v\n", conn.RemoteAddr().String(), conn)
				log.Infof("客户端%v 连接成功，conn=%v", conn.RemoteAddr().String(), conn)
			}
			go process(conn)
		}
	}()
	if D.DBStorage == "true" {
		// 数据库连接
		dbURL := fmt.Sprintf("%s:%s@(%s:%s)/%s?charset=%s", D.DbPara.User, D.DbPara.Pwd, D.DbPara.IP, D.DbPara.Port, D.DbPara.Database, D.DbPara.Charset)
		err = db.MkDbConn(D.DbPara.Type, dbURL)
		if err != nil {
			log.Error("init engine error", err)
			return
		}

		// 资源项初始化
		err = R.Init()
		if err != nil {
			log.Error("init resources error: ", err)
			return
		}
	}
	if httpManage == "true" {

		// API资源初始化
		handler, err := api.NewHandler(cfgPath, ServerIp+":"+ServerWebPort)
		if err != nil {
			log.Error("init api handler error: ", err)
			return
		}
		if err = handler.Start(); err != nil {
			log.Error("start api handler error: ", err)
			return
		}

		// 阻塞主线程，等待退出
		log.Info("waiting for exit...")
		sign := make(chan os.Signal)
		signal.Ignore(syscall.SIGPIPE, syscall.SIGALRM)
		signal.Notify(sign, syscall.SIGINT, syscall.SIGTERM)

		// exit
		log.Warn("Receive signal", (<-sign).String())
		//R.Stop()
		_ = handler.Stop()
		<-time.After(time.Second)
	} else {
		// 阻塞主线程，等待退出
		log.Info("waiting for exit...")
		sign := make(chan os.Signal)
		signal.Ignore(syscall.SIGPIPE, syscall.SIGALRM)
		signal.Notify(sign, syscall.SIGINT, syscall.SIGTERM)

		// exit
		log.Warn("Receive signal", (<-sign).String())
	}
}
